#pragma once

#include "stdafx.h"

#include <time.h>
#include <vector>
#include <string>
#include <fstream>
#include <memory>
#include <queue>
#include <map>

#include "Geo.h"

using namespace std;
using namespace Geo;

namespace Tpq
{
#pragma pack(4)
	struct ImageMatrixHeader
	{
		char	extension[4];		// .jpg or .png
		int		bitsPerPixel;		// How many bits are used per pixel (usually 24 for jpg and 8 for png)
		int		pixelsPerEntity;	// How many pixels make up an entity. It's either 1 or 2
		int		columns;			// How many columns are there in this image matrix
		int		rows;				// How many rows are there in this image matrix
		int		width;				// Width of the map in pixels (dimension is for full image for the map unless flags==256, but individual images for the elevation & shade)
		int		height;				// Height of the map in pixels
		int		flags;				// Haven't figured out. 256 = width & height are for individual maplets
	};

#pragma pack(4)
	struct Header
	{
		int		magicNumber;		// Always 0x00000001
		double	west;				// West longitude, in degrees
		double	north;				// North latitude, in degrees
		double	east;				// East longitude, in degrees
		double	south;				// South latitude, in degrees
		char	seriesName[220];	// Always "TOPO!"
		char	quadName[128];
		char	areaName[32];
		char	source[32];			// E.g. "USGS"
		char	mapVersion[4];		// Year when the map was printed
		char	mapCurrent[4];		// Year when the data was collected
		char	contourInterval[8];
		char	_pad2[16];
		ImageMatrixHeader mapImageInfo;
		char	elevationDataDesc[80];
		int		minElevation;		// min and max elevation, in meters, plus 0x8000 to avoid negatives
		int		maxElevation;
		ImageMatrixHeader elevationImageInfo;
		char	_pad3[28];
		ImageMatrixHeader shadeImageInfo;
		char	_pad4[332];
	};

	struct MapId
	{
		short level;
		int south;
		int east;

		inline bool operator == (const MapId& other) const
		{
			return level == other.level && south == other.south && east == other.east;
		}

		inline bool operator < (const MapId& other) const
		{
			return level < other.level || (level == other.level && (south < other.south || (south == other.south && east < other.east)));
		}
	};

	struct RenderedMapletKey
	{
		MapId id;
		short row;
		short column;
	};

	struct RenderedMaplet
	{
		double	yScale;
		clock_t lastAccessTime;
		shared_ptr<CDC> bufferDC;

		RenderedMaplet()
			: lastAccessTime(0)
			, bufferDC(nullptr)
		{}
	};

	// MemoryBitmap class encapsulates a global memory handle and and a Gdiplus::Bitmap object
	// The user declares an instance of MemoryBitmap, calls GetBuffer() to get a pointer to the
	// data buffer, fills the buffer, and calls LoadBitmapFromBuffer() to load the Bitmap object.
	// After that, the MemoryBitmap object can be cast to a Bitmap* (via overloaded () operator)
	// or used directly as a Bitmap* (via overloaded -> operator).
	// MemoryBitmap implements a move constructor, but not a copy constructor or assignment operator
	class MemoryBitmap
	{
		HGLOBAL memory;
		LPVOID buffer;
		IStream* stream;
		unique_ptr<Bitmap> bitmap;
	public:
		MemoryBitmap(int size)
		{
			memory = GlobalAlloc(0, size);
			if (SUCCEEDED(CreateStreamOnHGlobal(memory, FALSE, &stream)))
				buffer = GlobalLock(memory);
			else
				throw "Failed to allocate memory in MemoryBitmap::MemoryBitmap()";
		}
		MemoryBitmap(MemoryBitmap&& other)
		{
			bitmap = move(other.bitmap);
			stream = other.stream;
			other.stream = nullptr;
			memory = other.memory;
			other.memory = 0;
		}
		~MemoryBitmap()
		{
			if (stream != nullptr)
				stream->Release();
			if (memory != 0)
				GlobalFree(memory);
		}
		char* GetBuffer()
		{
			return reinterpret_cast<char*>(buffer);
		}
		void LoadBitmapFromBuffer()
		{
			bitmap.reset(Bitmap::FromStream(stream));
		}
		Bitmap* operator -> () const
		{
			return bitmap.get();
		}
		operator Bitmap* () const
		{
			return bitmap.get();
		}
	};

	class TpqMap
	{
	public:
		MapId id;
		
		void LoadFile(CDC& renderDC, string filename);
		pair<double, double> GetNativeScale();
		void Draw(CDC& dc, GRect<double> rectWorld, Transformation worldToScreen, bool loadCacheOnly=false);
		void RenderMaplet(CDC& dc, RenderedMapletKey key, RenderedMaplet& maplet);
		bool HasPendingNativeMaplets();
		void LoadAllNativeMaplets();

	private:
		Header					header;
		string					filename;
		fstream					inputFile;
		
		vector<vector<short>>	elevationData;			// Elevation data
		vector<int>				elevationImageOffsets;	// Offsets to elevation images

		vector<int>				mapImageOffsets;	// Offsets to all maplets in the input file.
		GSize<double>			blockSizeWorld;		// Size of each block in world coordinate, in seconds.
		GSize<int>				blockSizeNative;	// Size of each block in local coordinate, in pixels.
		pair<double,double>		nativeScale;		// Native scale of the map (pixel/second).

		vector<bool>			nativeMapletBitset;		// Bitset keeping track of loaded native maplets.
		int						nativeMapletsLoaded;	// # of native maplets loaded
		shared_ptr<Bitmap>		nativeFullMap;			// Full map image stitched from maplets, native resolution.

		shared_ptr<Bitmap>		shadeImage;
		GSize<int>				shadeImageSize;

		MemoryBitmap CreateBitmapFromFile(int offset, int size);
		void LoadNativeMaplet(int row, int col);
		void LoadElevationData();
	};
}